package com.guosen.zebra.framework.starter.ngx.api.impl;


import com.guosen.zebra.framework.starter.ngx.api.NgxClient;
import com.guosen.zebra.framework.starter.ngx.config.NgxConfig;
import com.guosen.zebra.framework.starter.ngx.exception.NgxException;
import com.guosen.zebra.framework.starter.ngx.internal.UniNgxService;
import com.guosen.zebra.framework.starter.ngx.model.*;

/**
 * 安渡客户端实现
 */
public class NgxClientImpl implements NgxClient {

    /*
     * 对于所有调用，如果返回401，则表示token过期，需再次获取token，然后重试。
     */

    /**
     * token验证失败或者接口失效，需要重新发起认证
     */
    private static final String CODE_UNAUTHORIZED = "401";

    /**
     * 是否需要获取token
     */
    private volatile boolean needGetToken = true;

    /**
     * 缓存的token
     */
    private volatile String token = null;

    private UniNgxService uniNgxService;

    private NgxConfig ngxConfig;

    public NgxClientImpl(UniNgxService uniNgxService,
                         NgxConfig ngxConfig) {
        this.uniNgxService = uniNgxService;
        this.ngxConfig = ngxConfig;
    }

    @Override
    public ListResult list(ListParameter listParameter) throws NgxException {
        ListResult listResult;
        try {
            InvokeContext invokeContext = buildInvokeContext();
            listResult = uniNgxService.list(listParameter, invokeContext);
        }
        catch (NgxException e) {
            if (CODE_UNAUTHORIZED.equals(e.getCode())) {
                InvokeContext invokeContext = rebuildInvokeContext();
                listResult = uniNgxService.list(listParameter, invokeContext);
            }
            else {
                throw e;
            }
        }

        return listResult;
    }


    @Override
    public UploadResult upload(UploadParameter uploadParameter) throws NgxException {
        UploadResult uploadResult;
        try {
            InvokeContext invokeContext = buildInvokeContext();
            uploadResult = uniNgxService.upload(uploadParameter, invokeContext);
        }
        catch (NgxException e) {
            if (CODE_UNAUTHORIZED.equals(e.getCode())) {
                InvokeContext invokeContext = rebuildInvokeContext();
                uploadResult = uniNgxService.upload(uploadParameter, invokeContext);
            }
            else {
                throw e;
            }
        }

        return uploadResult;
    }

    @Override
    public DownloadResult download(DownloadParameter downloadParameter) throws NgxException {
        DownloadResult downloadResult;
        try {
            InvokeContext invokeContext = buildInvokeContext();
            downloadResult = uniNgxService.download(downloadParameter, invokeContext);
        }
        catch (NgxException e) {
            if (CODE_UNAUTHORIZED.equals(e.getCode())) {
                InvokeContext invokeContext = rebuildInvokeContext();
                downloadResult = uniNgxService.download(downloadParameter, invokeContext);
            }
            else {
                throw e;
            }
        }

        return downloadResult;
    }

    @Override
    public MkdirResult mkdir(MkdirParameter mkdirParameter) throws NgxException {
        MkdirResult mkdirResult;
        try {
            InvokeContext invokeContext = buildInvokeContext();
            mkdirResult = uniNgxService.mkdir(mkdirParameter, invokeContext);
        }
        catch (NgxException e) {
            if (CODE_UNAUTHORIZED.equals(e.getCode())) {
                InvokeContext invokeContext = rebuildInvokeContext();
                mkdirResult = uniNgxService.mkdir(mkdirParameter, invokeContext);
            }
            else {
                throw e;
            }
        }

        return mkdirResult;
    }

    @Override
    public MoveResult move(MoveParameter moveParameter) throws NgxException {
        MoveResult moveResult;
        try {
            InvokeContext invokeContext = buildInvokeContext();
            moveResult = uniNgxService.move(moveParameter, invokeContext);
        }
        catch (NgxException e) {
            if (CODE_UNAUTHORIZED.equals(e.getCode())) {
                InvokeContext invokeContext = rebuildInvokeContext();
                moveResult = uniNgxService.move(moveParameter, invokeContext);
            }
            else {
                throw e;
            }
        }

        return moveResult;
    }

    @Override
    public void delete(DeleteParameter deleteParameter) throws NgxException {
        try {
            InvokeContext invokeContext = buildInvokeContext();
            uniNgxService.delete(deleteParameter, invokeContext);
        }
        catch (NgxException e) {
            if (CODE_UNAUTHORIZED.equals(e.getCode())) {
                InvokeContext invokeContext = rebuildInvokeContext();
                uniNgxService.delete(deleteParameter, invokeContext);
            }
            else {
                throw e;
            }
        }
    }

    @Override
    public void destroy(DestroyParameter destroyParameter) throws NgxException {
        try {
            InvokeContext invokeContext = buildInvokeContext();
            uniNgxService.destroy(destroyParameter, invokeContext);
        }
        catch (NgxException e) {
            if (CODE_UNAUTHORIZED.equals(e.getCode())) {
                InvokeContext invokeContext = rebuildInvokeContext();
                uniNgxService.destroy(destroyParameter, invokeContext);
            }
            else {
                throw e;
            }
        }
    }

    private InvokeContext buildInvokeContext() throws NgxException {
        InvokeContext invokeContext = new InvokeContext();
        invokeContext.setAccount(ngxConfig.getAccount());
        invokeContext.setToken(getToken());
        invokeContext.setAddress(ngxConfig.getAddress());

        return invokeContext;
    }

    private InvokeContext rebuildInvokeContext() throws NgxException {
        InvokeContext invokeContext = new InvokeContext();
        invokeContext.setAccount(ngxConfig.getAccount());
        invokeContext.setToken(refreshAndGetToken());
        invokeContext.setAddress(ngxConfig.getAddress());

        return invokeContext;
    }

    private String getToken() throws NgxException {
        if (!needGetToken) {
            return token;
        }

        // 使用volatile flag来确定是否要getToken可能会多get几次，但是没有影响。

        String account = ngxConfig.getAccount();
        String password = ngxConfig.getPassword();
        String address = ngxConfig.getAddress();

        token = uniNgxService.token(account, password, address);
        needGetToken = false;
        return token;
    }

    private String refreshAndGetToken() throws NgxException {
        needGetToken = true;
        return getToken();
    }
}
